Skip to main content

VOIP & Video Consultation

VoipConfig Struct​

For VOIP consultation, the config data will be in voipConfig, and for VIDEO it will be in videoConfig. They share the same data structure.

struct VoipConfig {
var id: Int?
var consultationId: Int?
var apiKey: String?
var callId: String?
var token: String?
}

VOIP and Video consultations are handled similarly. The primary difference is enabling video for Video consultations and disabling it for VOIP consultations.

Frameworks​

We use OpenTok (Vonage Video API) to handle the VOIP/Video session. You need to import both AltibbiTelehealth and OpenTok.

import UIKit
import AltibbiTelehealth
import OpenTok

Static Properties​

In your view controller, declare properties to manage the session, publisher, and subscriber.

// The Consultation data object
var consultationInfo: Consultation?

// The OpenTok session
lazy var session: OTSession? = {
let config = (consultationInfo?.medium == "voip") ? consultationInfo?.voipConfig : consultationInfo?.videoConfig
guard let apiKey = config?.apiKey, let callId = config?.callId else { return nil }
return OTSession(apiKey: apiKey, sessionId: callId, delegate: self)
}()

// The OpenTok Publisher
lazy var publisher: OTPublisher? = {
let settings = OTPublisherSettings()
settings.name = UIDevice.current.name
if consultationInfo?.medium == "voip" {
settings.videoTrack = false
}
return OTPublisher(delegate: self, settings: settings)
}()

// The OpenTok Subscriber
var subscriber: OTSubscriber?

VOIP/Video Call Actions​

Manage common call actions like muting, switching cameras, and ending the call.

// Handle Mute/Unmute
@IBAction func muteButtonTapped(_ sender: UIButton) {
publisher?.publishAudio = !(publisher?.publishAudio ?? true)
let title = (publisher?.publishAudio ?? true) ? "Mute" : "Unmute"
sender.setTitle(title, for: .normal)
}

// Handle Camera Source
@IBAction func switchCameraButtonTapped(_ sender: UIButton) {
publisher?.cameraPosition = (publisher?.cameraPosition == .front) ? .back : .front
}

// Handle Enable/Disable Video
@IBAction func toggleVideoButtonTapped(_ sender: UIButton) {
publisher?.publishVideo = !(publisher?.publishVideo ?? true)
}

// Handle End Call
@IBAction func endCallButtonTapped(_ sender: UIButton) {
var error: OTError?
session?.disconnect(&error)

if let consultationId = consultationInfo?.consultationId {
ApiService.cancelConsultation(id: consultationId) { (cancelled, failure, error) in
// Handle cancellation callback
}
}
}

Connection Implementation​

Call doConnect() in your viewDidLoad() or equivalent setup method.

fileprivate func doConnect() {
var error: OTError?
let token = (consultationInfo?.medium == "voip") ? consultationInfo?.voipConfig?.token : consultationInfo?.videoConfig?.token

session?.connect(withToken: token ?? "", error: &error)
if let error = error {
print("Connection error: \(error.localizedDescription)")
}
}

fileprivate func doPublish() {
guard let session = session, let publisher = publisher else { return }
var error: OTError?
session.publish(publisher, error: &error)

if let pubView = publisher.view {
// Adjust frame and add to your view hierarchy
pubView.frame = CGRect(x: 0, y: 0, width: 320, height: 240)
self.view.addSubview(pubView)
}
}

fileprivate func doSubscribe(_ stream: OTStream) {
var error: OTError?
subscriber = OTSubscriber(stream: stream, delegate: self)
session?.subscribe(subscriber!, error: &error)
}

OpenTok Delegates​

Implement the delegates to handle session connectivity, streaming, and publisher/subscriber events.

extension YourViewController: OTSessionDelegate {
func sessionDidConnect(_ session: OTSession) {
doPublish()
}

func sessionDidDisconnect(_ session: OTSession) {
print("Session disconnected")
}

func session(_ session: OTSession, streamCreated stream: OTStream) {
if subscriber == nil {
doSubscribe(stream)
}
}

func session(_ session: OTSession, streamDestroyed stream: OTStream) {
if subscriber?.stream?.streamId == stream.streamId {
subscriber?.view?.removeFromSuperview()
subscriber = nil
}
}

func session(_ session: OTSession, didFailWithError error: OTError) {
print("Session failed: \(error.localizedDescription)")
}
}

extension YourViewController: OTPublisherDelegate {
func publisher(_ publisher: OTPublisherKit, streamCreated stream: OTStream) {
print("Publishing started")
}

func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) {
print("Publisher failed: \(error.localizedDescription)")
}
}

extension YourViewController: OTSubscriberDelegate {
func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) {
if let subsView = subscriber?.view {
subsView.frame = CGRect(x: 0, y: 240, width: 320, height: 240)
self.view.addSubview(subsView)
}
}

func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) {
print("Subscriber failed: \(error.localizedDescription)")
}
}